#include <cstdio>
#include <cstdlib>
#include <errno.h>

#if defined(__ANDROID__)
#define RTC_LOG_TAG "rtc"
#include <android/log.h> // NOLINT

//Iftah: imports for stack trace code taken from  https://stackoverflow.com/a/35585744/519995
#include <unwind.h>
#include <dlfcn.h>
#include <cxxabi.h>

#else
// iftah - moved this into NOT android - since execinfo.h isn't available
#if defined(__GLIBCXX__) && !defined(__UCLIBC__)
#include <cxxabi.h>
#include <execinfo.h>
#endif
#endif

#if defined(_WIN32)
#include <windows.h>

#define LAST_SYSTEM_ERROR \
	(::GetLastError())

#else

#define LAST_SYSTEM_ERROR \
	(errno)

#endif

#include "jni_checks.h"



#if defined(_MSC_VER)
// Warning C4722: destructor never returns, potential memory leak.
// FatalMessage's dtor very intentionally aborts.
#pragma warning(disable : 4722)
#endif

namespace rtc
{

	void VPrintError(const char *format, va_list args)
	{
#if defined(__ANDROID__)
		__android_log_vprint(ANDROID_LOG_ERROR, RTC_LOG_TAG, format, args);
#else
		vfprintf(stderr, format, args);
#endif
	}

	void PrintError(const char *format, ...)
	{
		va_list args;
		va_start(args, format);
		VPrintError(format, args);
		va_end(args);
	}

#if defined(__ANDROID__)
//Iftah: taken from  https://stackoverflow.com/a/35585744/519995
#include <unwind.h>
#include <dlfcn.h>
#include <cxxabi.h>

	struct android_backtrace_state
	{
		void **current;
		void **end;
	};

	_Unwind_Reason_Code android_unwind_callback(struct _Unwind_Context *context,
																							void *arg)
	{
		android_backtrace_state *state = (android_backtrace_state *)arg;
		uintptr_t pc = _Unwind_GetIP(context);
		if (pc)
		{
			if (state->current == state->end)
			{
				return _URC_END_OF_STACK;
			}
			else
			{
				*state->current++ = reinterpret_cast<void *>(pc);
			}
		}
		return _URC_NO_REASON;
	}

	void android_dump_stack(void)
	{

		const int max = 100;
		void *buffer[max];

		android_backtrace_state state;
		state.current = buffer;
		state.end = buffer + max;

		_Unwind_Backtrace(android_unwind_callback, &state);

		int count = (int)(state.current - buffer);

		for (int idx = 0; idx < count; idx++)
		{
			const void *addr = buffer[idx];
			const char *symbol = "";

			Dl_info info;
			if (dladdr(addr, &info) && info.dli_sname)
			{
				symbol = info.dli_sname;
			}
			int status = 0;
			char *demangled = __cxxabiv1::__cxa_demangle(symbol, 0, 0, &status);

			PrintError("%03d: 0x%p %s",
					idx,
					addr,
					(NULL != demangled && 0 == status) ? demangled : symbol);

			if (NULL != demangled)
				free(demangled);
		}
	}
#endif

	// TODO(ajm): This works on Mac (although the parsing fails) but I don't seem
	// to get usable symbols on Linux. This is copied from V8. Chromium has a more
	// advanced stace trace system; also more difficult to copy.
	void DumpBacktrace()
	{
#if defined(__ANDROID__)
		android_dump_stack();
#else
// iftah: moved this into NOT android - since backtrace isn't available
#if defined(__GLIBCXX__) && !defined(__UCLIBC__)
		void *trace[100];
		int size = backtrace(trace, sizeof(trace) / sizeof(*trace));
		char **symbols = backtrace_symbols(trace, size);
		PrintError("\n==== C stack trace ===============================\n\n");
		if (size == 0)
		{
			PrintError("(empty)\n");
		}
		else if (symbols == nullptr)
		{
			PrintError("(no symbols)\n");
		}
		else
		{
			for (int i = 1; i < size; ++i)
			{
				char mangled[201];
				if (sscanf(symbols[i], "%*[^(]%*[(]%200[^)+]", mangled) == 1)
				{ // NOLINT
					PrintError("%2d: ", i);
					int status;
					size_t length;
					char *demangled =
							abi::__cxa_demangle(mangled, nullptr, &length, &status);
					PrintError("%s\n", demangled != nullptr ? demangled : mangled);
					free(demangled);
				}
				else
				{
					// If parsing failed, at least print the unparsed symbol.
					PrintError("%s\n", symbols[i]);
				}
			}
		}
		free(symbols);
#endif
#endif
	}

	FatalMessage::FatalMessage(const char *file, int line)
	{
		Init(file, line);
	}

	FatalMessage::FatalMessage(const char *file, int line, std::string *result)
	{
		Init(file, line);
		stream_ << "Check failed: " << *result << std::endl
				<< "# ";
		delete result;
	}

	FatalMessage::~FatalMessage()
	{
		fflush(stdout);
		fflush(stderr);
		stream_ << std::endl
				<< "#" << std::endl;
		PrintError(stream_.str().c_str());
		DumpBacktrace();
		fflush(stderr);
		abort();
	}

	void FatalMessage::Init(const char *file, int line)
	{
		stream_ << std::endl
				<< std::endl
				<< "#" << std::endl
				<< "# Fatal error in " << file << ", line " << line << std::endl
				<< "# last system error: " << LAST_SYSTEM_ERROR << std::endl
				<< "# ";
	}

} // namespace rtc

// Function to call from the C version of the RTC_CHECK and RTC_DCHECK macros.
void rtc_FatalMessage(const char *file, int line, const char *msg)
{
	rtc::FatalMessage(file, line).stream() << msg;
}
